package com.ben.multiple.jsoup.service;

import com.ben.multiple.jsoup.BeanMapper;
import com.ben.multiple.jsoup.Config;
import com.ben.multiple.jsoup.JsonMapper;
import com.ben.multiple.jsoup.entity.Address;
import com.ben.multiple.jsoup.model.AddressModel;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;

/**
 * @author BenJin Yin
 * @date 2020/5/5
 */
@Service
@Slf4j
@AllArgsConstructor
public class AddressServiceImpl {

    private final Config config;

    public List<Address> jsoupAddress() {
        String host = "http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2019/";
        String index = "index.html";
        String nationCode = "86";
        String provinceClass = "provincetr";
        String cityClass = "citytr";
        String countyClass = "countytr";
        String townClass = "towntr";
        String villageClass = "villagetr";
        CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
        // 获取省份
        List<AddressModel> provinceList = this.getProvinceAddress(host, index, provinceClass);
        provinceList.forEach(obj -> obj.setParentId(nationCode));
        // 获取市
        List<AddressModel> cityList = new ArrayList<>();
        for (AddressModel province : provinceList) {
            List<AddressModel> city = this.getCityList(host, cityClass, province);
            cityList.addAll(city);
        }
        // 获取区
        List<AddressModel> countyList = new ArrayList<>();
        List<AddressModel> gds = new ArrayList<>();
        for (AddressModel city : cityList) {
            if (city.getHref() == null) {
                continue;
            }
            // 东莞市、中山市没有区，直接是街道
            if (city.getName().equals("东莞市") || city.getName().equals("中山市")) {
                String url = host + city.getHref();
                Document document = this.getDocument(url);
                Elements elements = document.getElementsByClass(townClass);
                List<AddressModel> addressModelList = new ArrayList<>();
                for (Element element : elements) {
                    Elements a = element.getElementsByTag("a");
                    if (a == null || a.isEmpty()) {
                        // 无 a 标签
                        Elements td = element.getElementsByTag("td");
                        String code = td.first().text();
                        String name = td.last().text();
                        addressModelList.add(new AddressModel(code, name, city.getId(), city.getProvinceId(), "3", null));
                    } else {
                        Element first = a.first();
                        String href = first.attributes().get("href");
                        String code = first.text();
                        String name = a.last().text();
                        addressModelList.add(new AddressModel(code, name, city.getId(), city.getProvinceId(), "3", href));
                    }
                }
                gds.addAll(addressModelList);
            } else {
                List<AddressModel> county = this.getCountyList(host, countyClass, city);
                countyList.addAll(county);
            }
        }
        // 街道
        log.info("请求街道");
        List<AddressModel> townList = new ArrayList<>();
        for (AddressModel county : countyList) {
            if (county.getHref() == null) {
                continue;
            }
            List<AddressModel> town = this.getTownList(host, townClass, county);
            townList.addAll(town);
        }
        log.info("街道数量：{}", townList.size());
        // 社区
        log.info("请求社区");
        // 区的 id 和 链接
        Map<String, String> countyMap = countyList.stream().filter(obj -> obj.getHref() != null)
                .collect(Collectors.toMap(AddressModel::getId, AddressModel::getHref));
        List<AddressModel> villageList = new ArrayList<>();
        for (AddressModel town : townList) {
            if (town.getHref() == null) {
                continue;
            }
            // 社区路径需要拼接 区的 href 的前面部分
            String[] split = countyMap.get(town.getParentId()).split("/");
            List<AddressModel> village = this.getVillageList(host, villageClass, town, split[0]);
            villageList.addAll(village);
        }
        log.info("社区数量：{}", villageList.size());
        // 获取东莞、中山下面的街道的社区
        List<AddressModel> gdVillageList = new ArrayList<>();
        for (AddressModel town : gds) {
            if (town.getHref() == null) {
                continue;
            }
            List<AddressModel> village = this.getGdVillageList(host, villageClass, town);
            gdVillageList.addAll(village);
        }
        // 数据重组
        List<Address> addressList = new ArrayList<>();
        addressList.addAll(BeanMapper.mapAsList(provinceList, AddressModel.class, Address.class));
        addressList.addAll(BeanMapper.mapAsList(cityList, AddressModel.class, Address.class));
        addressList.addAll(BeanMapper.mapAsList(countyList, AddressModel.class, Address.class));
        addressList.addAll(BeanMapper.mapAsList(townList, AddressModel.class, Address.class));
        addressList.addAll(BeanMapper.mapAsList(villageList, AddressModel.class, Address.class));
        // 东莞、中山
        addressList.addAll(BeanMapper.mapAsList(gds, AddressModel.class, Address.class));
        addressList.addAll(BeanMapper.mapAsList(gdVillageList, AddressModel.class, Address.class));
        log.info("总数量：{}", addressList.size());
        log.info(JsonMapper.defaultMapper().toJson(addressList));
        return addressList;
    }


    private List<AddressModel> getGdVillageList(String host, String villageClass, AddressModel town) {
        String url = host + town.getProvinceId() + "/" + town.getHref();
        Document document = this.getDocument(url);
        Elements elements = document.getElementsByClass(villageClass);
        List<AddressModel> addressModelList = new ArrayList<>();
        for (Element element : elements) {
            Elements td = element.getElementsByTag("td");
            if (td != null && !td.isEmpty()) {
                String code = td.first().text();
                String name = td.last().text();
                addressModelList.add(new AddressModel(code, name, town.getId(), town.getProvinceId(), "4", null));
            }
        }
        return addressModelList;
    }

    private List<AddressModel> getVillageList(String host, String villageClass, AddressModel town, String p) {
        String url = host + town.getProvinceId() + "/" + p + "/" + town.getHref();
        Document document = this.getDocument(url);
        Elements elements = document.getElementsByClass(villageClass);
        List<AddressModel> addressModelList = new ArrayList<>();
        for (Element element : elements) {
            Elements td = element.getElementsByTag("td");
            if (td != null && !td.isEmpty()) {
                String code = td.first().text();
                String name = td.last().text();
                addressModelList.add(new AddressModel(code, name, town.getId(), town.getProvinceId(), "5", null));
            }
        }
        return addressModelList;
    }

    private List<AddressModel> getTownList(String host, String townClass, AddressModel county) {
        String url = host + county.getProvinceId() + "/" + county.getHref();
        Document document = this.getDocument(url);
        Elements elements = document.getElementsByClass(townClass);
        List<AddressModel> addressModelList = new ArrayList<>();
        for (Element element : elements) {
            Elements a = element.getElementsByTag("a");
            if (a == null || a.isEmpty()) {
                // 无 a 标签
                Elements td = element.getElementsByTag("td");
                String code = td.first().text();
                String name = td.last().text();
                addressModelList.add(new AddressModel(code, name, county.getId(), county.getProvinceId(), "4", null));
            } else {
                Element first = a.first();
                String href = first.attributes().get("href");
                String code = first.text();
                String name = a.last().text();
                addressModelList.add(new AddressModel(code, name, county.getId(), county.getProvinceId(), "4", href));
            }
        }
        return addressModelList;
    }

    private List<AddressModel> getCountyList(String host, String countyClass, AddressModel city) {
        String url = host + city.getHref();
        Document document = this.getDocument(url);
        Elements elements = document.getElementsByClass(countyClass);
        List<AddressModel> addressModelList = new ArrayList<>();
        for (Element element : elements) {
            Elements a = element.getElementsByTag("a");
            if (a == null || a.isEmpty()) {
                // 无 a 标签
                Elements td = element.getElementsByTag("td");
                String code = td.first().text();
                String name = td.last().text();
                addressModelList.add(new AddressModel(code, name, city.getId(), city.getProvinceId(), "3", null));
            } else {
                Element first = a.first();
                String href = first.attributes().get("href");
                String code = first.text();
                String name = a.last().text();
                addressModelList.add(new AddressModel(code, name, city.getId(), city.getProvinceId(), "3", href));
            }
        }
        return addressModelList;
    }

    private List<AddressModel> getCityList(String host, String cityClass, AddressModel province) {
        String url = host + province.getHref();
        Document document = this.getDocument(url);
        Elements elements = document.getElementsByClass(cityClass);
        List<AddressModel> addressModelList = new ArrayList<>();
        for (Element element : elements) {
            Elements aList = element.getElementsByTag("a");
            Element first = aList.first();
            String href = first.attributes().get("href");
            String code = first.text();
            String name = aList.last().text();
            addressModelList.add(new AddressModel(code, name, province.getId(), province.getId(), "2", href));
        }
        return addressModelList;
    }

    private List<AddressModel> getProvinceAddress(String host, String index, String provinceClass) {
        String url = host + index;
        Document document = this.getDocument(url);
        Elements elements = document.getElementsByClass(provinceClass);
        List<Element> elementList = new ArrayList<>();
        for (Element element : elements) {
            elementList.addAll(element.getElementsByTag("a"));
        }
        List<AddressModel> addressModelList = new ArrayList<>();
        for (Element element : elementList) {
            String href = element.attributes().get("href");
            String[] split = href.split("\\.");
            String text = element.text();
            addressModelList.add(new AddressModel(split[0], text, null, split[0], "1", href));
        }
        return addressModelList;
    }

    private Document getDocument(String url) {
        // 增加随机延迟
        int i = new Random().nextInt(2) + 1;
        try {
            Thread.sleep(i * 10);
        } catch (InterruptedException e) {
            log.error("主线程异常");
            e.printStackTrace();
        }
        Document document;
        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
            connection.setInstanceFollowRedirects(false);
            int responseCode = connection.getResponseCode();
            if (responseCode != 200) {
                log.error("responseCode is {}, url : {}", responseCode, url);
                return this.getDocument(url);
            }
            InputStream inputStream = connection.getInputStream();
            document = Jsoup.parse(inputStream, "GBK", url);
            if (inputStream != null) {
                inputStream.close();
            }
        } catch (IOException e) {
            log.error("internet connect fail, url : {}", url, e);
            return this.getDocument(url);
        }
        if (document == null || document.body() == null || document.body().getAllElements() == null
                || document.body().getAllElements().isEmpty()) {
            log.error("document is blank, url : {}", url);
            return this.getDocument(url);
        }
        return document;
    }

    private Document getDocumentP(String url) {
        // 增加随机延迟
        int i = new Random().nextInt(2) + 1;
        try {
            Thread.sleep(i * 10);
        } catch (InterruptedException e) {
            log.error("主线程异常, url: {}", url);
            e.printStackTrace();
        }
        Document document;
        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
            connection.setInstanceFollowRedirects(false);
            int responseCode = connection.getResponseCode();
            if (responseCode == 404) {
                log.error("responseCode is 404, return null, url : {}", url);
                return null;
            }
            if (responseCode != 200) {
                log.error("responseCode is {}, url : {}", responseCode, url);
                return this.getDocument(url);
            }
            InputStream inputStream = connection.getInputStream();
            document = Jsoup.parse(inputStream, "GBK", url);
            if (inputStream != null) {
                inputStream.close();
            }
        } catch (IOException e) {
            log.error("internet connect fail, url : {}", url, e);
            return this.getDocument(url);
        }
        if (document == null || document.body() == null || document.body().getAllElements() == null
                || document.body().getAllElements().isEmpty()) {
            log.error("document is blank, url : {}", url);
            return this.getDocument(url);
        }
        return document;
    }
}
